/** Copyright 2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.spring4facelets.security.ui;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import javax.faces.component.UIComponent;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.security.Authentication;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.context.SecurityContextHolder;

import com.sun.facelets.FaceletContext;
import com.sun.facelets.tag.TagAttribute;
import com.sun.facelets.tag.TagConfig;
import com.sun.facelets.tag.TagHandler;

/**
 * Outputs the tag's body if the authenicated user's granted authorities meets
 * the requirements specified by the tag's attributes.
 *
 * @author Allen Parslow
 */
public class AuthorizeTagHandler extends TagHandler {

    private static final Collection<String> EMPTY_LIST = Collections.unmodifiableList(new ArrayList<String>(0));

    private final TagAttribute ifAllGrantedExpression;
    private final TagAttribute ifAnyGrantedExpression;
    private final TagAttribute ifNotGrantedExpression;

    /**
     * Create a new AuthorizeTagHandler instance, extracting
     * attribute-expressions from the specified TagConfig.
     *
     * @param config
     *            Configuration for the TagHandler.
     */
    public AuthorizeTagHandler(TagConfig config) {
        super(config);
        this.ifAllGrantedExpression = this.getAttribute("ifAllGranted");
        this.ifAnyGrantedExpression = this.getAttribute("ifAnyGranted");
        this.ifNotGrantedExpression = this.getAttribute("ifNotGranted");
    }

    /**
     * {@inheritDoc}
     */
    public void apply(FaceletContext ctx, UIComponent parent) throws IOException {

        Collection<String> ifAllGranted = toCollection(ifAllGrantedExpression, ctx);
        Collection<String> ifAnyGranted = toCollection(ifAnyGrantedExpression, ctx);
        Collection<String> ifNotGranted = toCollection(ifNotGrantedExpression, ctx);

        boolean granted = authorize(ifAllGranted, ifAnyGranted, ifNotGranted);

        if (granted) {
            this.nextHandler.apply(ctx, parent);
        }
    }

    @SuppressWarnings("unchecked")
    protected static boolean authorize(Collection<String> ifAllGranted, Collection<String> ifAnyGranted,
            Collection<String> ifNotGranted) {

        if (ifAllGranted.size() == 0 && ifAnyGranted.size() == 0 && ifNotGranted.size() == 0) {
            return false;
        }

        Authentication auth = SecurityContextHolder.getContext().getAuthentication();

        Collection<String> roles = extractRoles(auth);

        Collection allGranted = CollectionUtils.intersection(ifAllGranted, roles);
        Collection anyGranted = CollectionUtils.intersection(ifAnyGranted, roles);
        Collection notGranted = CollectionUtils.intersection(ifNotGranted, roles);

        boolean granted = true;
        if (ifNotGranted.size() != 0 && notGranted.size() != 0) {
            granted = false;
        }
        if (ifAnyGranted.size() != 0 && anyGranted.size() == 0) {
            granted = false;
        }
        if (ifAllGranted.size() != 0 && ifAllGranted.size() != allGranted.size()) {
            granted = false;
        }

        return granted;
    }

    protected static Collection<String> toCollection(TagAttribute attribute, FaceletContext ctx) {
        if (attribute == null) {
            return EMPTY_LIST;
        }

        String text = attribute.getValue(ctx);

        if (StringUtils.isBlank(text)) {
            return EMPTY_LIST;
        }

        String[] items = StringUtils.split(text, ",");
        if (items.length == 0) {
            return EMPTY_LIST;
        }

        return Arrays.asList(items);
    }

    protected static Collection<String> extractRoles(Authentication auth) {
        // No auth returns an empty list of roles (and does not error)
        if (auth == null || auth.getAuthorities() == null || auth.getAuthorities().length < 1) {
            return EMPTY_LIST;
        }

        Set<String> roles = new HashSet<String>(3);
        for (GrantedAuthority grantedAuthority : auth.getAuthorities()) {
            if (grantedAuthority.getAuthority() != null) {
                roles.add(grantedAuthority.getAuthority());
            }
        }

        return roles;
    }
}
